/*
 * Created on Oct 15, 2004
 * Updated on Dec 11, 2008 by JDC
 */
package com.plumtree.custom;

import com.plumtree.openkernel.config.IOKContext;
import com.plumtree.openkernel.factory.OKConfigFactory;
import com.plumtree.server.IPTAdminCatalog;
import com.plumtree.server.IPTAuthSource;
import com.plumtree.server.IPTObjectManager;
import com.plumtree.server.IPTQueryResult;
import com.plumtree.server.IPTSession;
import com.plumtree.server.IPTUser;
import com.plumtree.server.PT_FILTEROPS;
import com.plumtree.server.PT_PROPIDS;
import com.plumtree.server.PortalObjectsFactory;
import com.plumtree.xpshared.config.ConfigPathResolver;

/**
 * @author Administrator
 */
public class ReorganizeUsersExternalOp {

	public static void main (String[] args) {
		// jdc: added case where args =3 
		if(args == null  || (args.length != 2  && args.length != 3))
		{
			System.out.println("Please specify an authentication source ID, administrator username and password!  The password is optional.");
			System.out.println("\tjava ReorganizeUsersExternalOp <authsource ID> <admin user name> <optional admin password>");
			System.exit(-1);
		}
			
		// aj: get args, parse them
		int nAuthSourceID = Integer.parseInt(args[0]);
		String sAdminUsername = args[1];
		String sAdminPassword;
		if(args.length == 3){
			sAdminPassword = args[2];
		} else {
			sAdminPassword = "";
		}
					
		IOKContext ptContext = OKConfigFactory.createInstance(ConfigPathResolver.GetOpenConfigPath(), "portal");
		// aj: init a portal instance
		PortalObjectsFactory.Init(ptContext);
		
		// aj: login as the administrator
		IPTSession pSession = PortalObjectsFactory.CreateSession();
		pSession.Connect(sAdminUsername, sAdminPassword, null);
		
		// aj: open the auth source, do not lock initially
		IPTAuthSource pAuthSource = 
			(IPTAuthSource) pSession.GetAuthSources().Open(nAuthSourceID, false);
		
		System.out.println("Opened auth source with id " + nAuthSourceID + " for processing.");
		
		// aj: get the default profiles map from the auth source, this map will tell us
		//     what groups map to what folders.  we will use this to put users in the
		//     correct folders and thus automate their subportal experience
		Object[] defaultProfilesGroupIDs = pAuthSource.GetDefaultProfilesMap_GroupIDs();
		Object[] defaultProfilesFolderIDs = pAuthSource.GetDefaultProfilesMap_FolderIDs();
		
		System.out.println("Default Profile Groups and Folders are as follows:");
		for(int i=0; i<defaultProfilesGroupIDs.length; i++)
		{
			System.out.println("\tGroup: " + defaultProfilesGroupIDs[i] + ",\tFolder: " + defaultProfilesFolderIDs[i]);
		}
		
		// aj: get all the users on this auth source
		IPTQueryResult result = getUsersOnAuthSource(nAuthSourceID, pSession);

		// aj: reconcile users
		int nAffected = reconcileUserFolders(
				result, 
				pSession, 
				defaultProfilesGroupIDs, 
				defaultProfilesFolderIDs, 
				pAuthSource.GetNewUserFolderID());
		
		// aj: print out how many users were modified...
		System.out.println("Finished processing the group/folder map.  A total of "
						+ nAffected + " users were moved.  A total of " + result.RowCount() + " total users were processed.");

	}
	
	/**
	 * getUsersOnAuthSource() will return an IPTQueryResult will all the users for the 
	 * given auth source id.  It does not return User objects, but instead simply returns
	 * the following information about each user:
	 * <ul>
	 * 	<li> PT_PROPIDS.PT_PROPID_OBJECTID - the user's object id
	 * 	<li> PT_PROPIDS.PT_PROPID_NAME - the name of each user
	 * 	<li> PT_PROPIDS.PT_PROPID_USER_UNIQUENAME - the unique identifier for each user
	 * </ul>
	 * 
	 * @param nAuthSourceID - auth source id to lookup
	 * @param pSession - a valid IPTSession
	 * @return
	 */
	private static IPTQueryResult getUsersOnAuthSource(int nAuthSourceID, IPTSession pSession)
	{
		// aj: load the list of users for this authsource. we do this using the 
		//     IPTQueryFilter interface. we only care about the objectid column,
		//     we will use these values to open each user object and check it.  we
		//     will also use the uniquename and name columns for logging.
		int propids =
			(PT_PROPIDS.PT_PROPID_OBJECTID 
					| PT_PROPIDS.PT_PROPID_NAME 
					| PT_PROPIDS.PT_PROPID_USER_UNIQUENAME);

		// aj: the query has only one clause, which matches this auth source
		Object[][] ptQueryFilter = new Object[3][1];
		ptQueryFilter[0][0] = new Integer(PT_PROPIDS.PT_PROPID_USER_AUTHSOURCE); // filter on auth source ID
		ptQueryFilter[1][0] = new Integer(PT_FILTEROPS.PT_FILTEROP_EQ);          // equals..
		ptQueryFilter[2][0] = new Integer(nAuthSourceID);                        // the auth source

		// aj: we need the user manager to do the query
		IPTObjectManager pUserManager = pSession.GetUsers();
		
		// aj: perform query
		IPTQueryResult result = pUserManager.Query(
				propids, 
				-1,         // aj: search all folders
				PT_PROPIDS.PT_PROPID_OBJECTID,
				0, 
				-1, 
				ptQueryFilter);
		
		return result;
	}
	
	/**
	 * This function will iterate through all the user objects it is provided with and
	 * attempt to reconcile the folder the user is in.  Since this is a singleton operation
	 * (i.e. non-bulk), it may take a long time.
	 * 
	 * @param pUsers - an IPTQueryResult that contains all the users to look through
	 * @param pSession - an IPTSession to work with
	 * @param defaultProfilesGroupIDs - default profiles group ids 
	 * @param defaultProfilesFolderIDs - default profiles folder ids
	 * @param nNewUserFolderID - folder that new users go in by default
	 * @return
	 */
	private static int reconcileUserFolders(
			IPTQueryResult pUsers, 
			IPTSession pSession, 
			Object[] defaultProfilesGroupIDs, 
			Object[] defaultProfilesFolderIDs,
			int nNewUserFolderID)
	{
		// aj: get and print count
		int nUsers = pUsers.RowCount();
		int nUsersMoved = 0;
		System.out.println("Processing " + nUsers + " total users.");
		
		// aj: get a valid user manager and admin catalog
		IPTObjectManager pUserManager = pSession.GetUsers();
		IPTAdminCatalog pAdminCatalog = pSession.GetAdminCatalog();
		
		for(int i=0; i<nUsers; i++)
		{
			// aj: get the user id
			int nUserID = pUsers.ItemAsInt(i, PT_PROPIDS.PT_PROPID_OBJECTID);
			String sUserName = pUsers.ItemAsString(i, PT_PROPIDS.PT_PROPID_NAME);
			String sUniqueName = pUsers.ItemAsString(i, PT_PROPIDS.PT_PROPID_USER_UNIQUENAME);
			
			// aj: open the user object, do not lock
			IPTUser pUser = (IPTUser)pUserManager.Open(nUserID, false);
			
			// aj: get a list of groups the user is in
			IPTQueryResult pUserGroupMemberships = pUser.QueryFlattenedGroups();
			
			// aj: get the total number of group memberships for this user
			int nNumGroupMemberships = pUserGroupMemberships.RowCount();
			
			// aj: we will update this var with the proper folder id for the users,
			//     by default this is just the current folder of the user
			int nProperFolderID = pUser.GetAdminFolderID();
			
			// aj: we need to know if the user matches _any_ group at all, if it doesn't
			//     we will move them to the default folder
			boolean bHitNoGroups = true;
			
			// aj: now iterate through the default profiles array and update
			//     this var as necessary
			for (int j = 0; j < defaultProfilesGroupIDs.length; j++) {
				// aj: grab the group id
				int nGroupID = ((Integer) defaultProfilesGroupIDs[j]).intValue();
				
				// aj: we will record our hit here, default is no-match
				boolean bHit = false;

				// aj: iterate through the memberships and check if the current group is amongst them
				for(int k = 0; k < nNumGroupMemberships; k++)
				{
					// aj: check the group memberships to see if the user is in this group
					int nGroupMembershipID = 
						pUserGroupMemberships.ItemAsInt(k, PT_PROPIDS.PT_PROPID_OBJECTID);
				
					// aj: got a hit
					if(nGroupMembershipID == nGroupID)
					{
						bHit = true;
						break;
					}
				}
				
				// aj: check status
				if(bHit)
				{
					//aj: if we have a hit, then we should update the folder id
					nProperFolderID = ((Integer) defaultProfilesFolderIDs[j]).intValue();
					
					//aj: set that at least one group matches
					bHitNoGroups = false;
					
					//aj: exit the loop as soon as we have a hit
					break;
				}
			}
			
			// aj: check that one group was hit
			if(bHitNoGroups)
			{
				nProperFolderID = nNewUserFolderID;
			}
			
			// aj: if the folder id is the same here, then we should move on
			if(nProperFolderID == pUser.GetAdminFolderID())
			{
				// aj: then just log we are good
				System.out.println("User identified by " + sUniqueName + " (" + sUserName + ") is in the correct folder.");
			} else {
				// aj: move the user from it's current location to the nProperFolderID()
				Object[][] arUserToMove = new Object[2][1];
				arUserToMove[0][0] = new Integer(1);
				arUserToMove[1][0] = new Integer(nUserID); 
				
				System.out.println("User identified by " + sUniqueName + " (" + sUserName + ") is being moved from folder " 
						+ pUser.GetAdminFolderID() + " to folder " + nProperFolderID + ".");
				pAdminCatalog.MoveObjectsBetweenAdminFolders(arUserToMove, pUser.GetAdminFolderID(), nProperFolderID);
				
				nUsersMoved++;
			}
		}
		
		// aj: return the total number of users moved
		return nUsersMoved;
	}
}